//
// Created by tang on 2022/1/10.
//

#include <cassert>
#include <GLFW/glfw3.h>
#include <thread>
#include "app/app.hpp"

#include <iostream>

#include "app/app_base.hpp"
#include "data/version.hpp"
#include "gui/form.hpp"
#include "imgui/imgui.h"
#include "imgui/imgui_impl_glfw.h"
#include "imgui/implot.h"
#include "imgui/imgui_impl_opengl3.h"
#include "data/app_config.hpp"
#include "event/events.hpp"
#include "utils/app_utils.hpp"
#include "fmt/format.h"
#include "upgrade/upgrade_client.hpp"
#include <utils/os_utils.hpp>
#include "app/version.h"
#include "sql/sql_data_extensions.hpp"
#include "integrations/redmine/redmine_apis.hpp"

std::vector<const DriverFactory*> Application::m_sql_drivers_;

static void glfw_error_callback(int error, const char* description)
{
    fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}

static void InitImGuiAndImPlot(GLFWwindow* window, const char* glsl_version) {

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImPlot::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;       // Enable Keyboard Controls

    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
    // io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;           // Enable Docking
    // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;         // Enable Multi-Viewport / Platform Windows
    // io.ConfigViewportsNoAutoMerge = true;
    // io.ConfigViewportsNoTaskBarIcon = true;
    io.IniFilename = nullptr;

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);
}

static void CleanImGuiAndImPlot() {

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImPlot::DestroyContext();
    ImGui::DestroyContext();
}

Application::Application() :
ApplicationBase(Version{
    .major = VERSION_MAJOR,
    .minor = VERSION_MINOR,
    .patch = VERSION_PATCH,
    .build_number = VERSION_BUILD_NUMBER,
}),
m_main_form_(nullptr), m_sql_driver_(nullptr), m_event_listener_ (Bus ()), m_running_ (true)
{
    glfwSetErrorCallback(glfw_error_callback);
    if (!glfwInit())
        abort();

    // fill sql data extension
    {
        sql_data_ext.get_issue_project_desc = &RedMine::GetIssueProjectDesc;
    }
}

Application::~Application() {
    glfwTerminate();
    delete m_sql_driver_;
    m_sql_driver_ = nullptr;
    m_event_listener_.unlistenAll();
}

//static double g_next_frame_time = 0;
static bool g_win_focused = true;
static void glfw_win_focus_callback(GLFWwindow* w, int focus)
{
    g_win_focused = focus;
    //if (focus)
    //    g_next_frame_time = glfwGetTime();
}

void Application::Run(MainFromBase* form) {
    assert(form != nullptr);
    m_main_form_ = form;

    // show window maximize if need
    bool const not_max = Value(Config::KEY_WINDOW_MAXIMIZE).empty();
    const char* glsl_version = nullptr;
    GLFWwindow* window = m_main_form_->CreateWindow(&glsl_version, !not_max);
    IM_ASSERT(window != nullptr && "Form not create window!!");

    glfwSetWindowFocusCallback(window, glfw_win_focus_callback);

    // init imgui context
    InitImGuiAndImPlot(window, glsl_version);

    m_main_form_->SetupStyles();

    bool font_changed = true;
    bool style_changed = true;
    double frame_delay = 1.0 / 30;

    const auto font_setup = [this]
    {
        static bool first_load = true;
        float fontSize = 18.0f;
        try
        {
            fontSize = static_cast<float>(stod(Value(Config::KEY_FONT_SIZE)));
        }
        catch (std::exception&) {}

        fontSize = std::clamp(fontSize, 10.f, 32.f);
        Value(Config::KEY_FONT_SIZE) = std::to_string(fontSize);

        if (first_load)
            m_main_form_->SetupFonts(fontSize);
        else
            ImGui::GetStyle().FontSizeBase = fontSize;
        ImGui::GetStyle().FramePadding = ImVec2{fontSize * .12f,fontSize * .12f};
        first_load = false;
    };

    const auto style_setup = [this]
    {
        auto style_name = Value(Config::KEY_APP_STYLE);

        if (style_name == "Dark")
            ImGui::StyleColorsDark();
        else if (style_name == "Light")
            ImGui::StyleColorsLight();
        else if (style_name == "Classic")
            ImGui::StyleColorsClassic();
        else
        {
            style_name = "Default";
            m_main_form_->SetupStyles();
        }

        Value(Config::KEY_APP_STYLE) = style_name;
    };

    auto io = ImGui::GetIO();

    // Main loop

    // Prepare main loop
    // m_main_form_->Init();
    m_event_listener_.listen<AppEvent::CopyTextEvent>([window](const AppEvent::CopyTextEvent & e) {
        glfwSetClipboardString(window, e.text.c_str());
    });
    m_event_listener_.listen<AppEvent::FontEvent>([&font_changed](const auto)
        {
            font_changed = true;
        });
    m_event_listener_.listen<AppEvent::StyleEvent>([&style_changed](const auto)
        {
            style_changed = true;
        });
    m_event_listener_.listen<AppEvent::AppUpgradeEvent>([](const auto& info)
        {
            if (info.update_flags & AppEvent::AppUpgradeEvent::eHasNewVersion)
            {
                SendInfo(fmt::format("有可用更新：\n\n{}", info.release_note));
                // Has Update
                SendNotificationEx(fmt::format("可从版本 {} 更新到版本 {}", info.current_version, info.latest_version), 0,
                    "马上更新", []() { download_latest_package(false); },
                    "暂不更新", [] {});
            }
            else
            {
                // No Update
                SendNotificationEx(fmt::format("程序已是最新版本（{}）", info.current_version), 3000,
                    "知道了", [] {});
            }
        });

    double last_keep_alive_time = 0;

    check_upgrade_async(true);

    glfwRequestWindowAttention(window);

    asio::error_code error;
    while (!glfwWindowShouldClose(window) && m_running_.load()) {
        if (font_changed)
        {
            font_setup();
            font_changed = false;
        }

        if (style_changed)
        {
            style_setup();
            style_changed = false;
        }

        if (g_win_focused) {
            glfwPollEvents();
            Delay(frame_delay / 2.0);
        }
        else
            glfwWaitEventsTimeout(0.05); // 50ms

        // new frame
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        if (ImGui::GetTime() - last_keep_alive_time > 10.0)
        {
            App->Sql()->KeepAlive();
            last_keep_alive_time = ImGui::GetTime();
        }

        // Some callbacks use ui code, so we put it inside frame rendering
        GetMainIO().run_one(error);
        if (error) {

            // TODO: Handle errors
        }
        Bus()->processLimit(1);

        // form rendering
        m_main_form_->OnDraw();

        // Rendering
        ImGui::Render();
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

        // Multi ViewPort
        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
        {
            GLFWwindow* backup_current_context = glfwGetCurrentContext();
            ImGui::UpdatePlatformWindows();
            ImGui::RenderPlatformWindowsDefault();
            glfwMakeContextCurrent(backup_current_context);
        }

        glfwSwapBuffers(window);

        // calc frame time
        //double use_delay = g_win_focused ? frame_delay : 0.5; // 2 FPS
        //g_next_frame_time = use_delay + start;
    }

    m_main_form_->Cleanup();

    // Cleanup ImGui
    CleanImGuiAndImPlot();

    m_main_form_->DestroyWindow(window);
    window = nullptr;
}

void Application::ShowDialog(const std::string& name) const {
    if (m_main_form_) {
        m_main_form_->ShowDialog(name);
    }
}

MainFromBase* Application::RootForm() const {
    return m_main_form_;
}

void Application::RegisterSqlDriver(const DriverFactory* factory) {
    if (factory != nullptr)
        m_sql_drivers_.emplace_back(factory);
}

void Application::SetupSqlDriver() {
    if (m_sql_driver_ == nullptr) {
        if (m_sql_drivers_.empty())
        {
            perror("无可用的SQL驱动！！");
            abort();
        }

        auto name = Value(Config::KEY_SQL_DRIVER_NAME);
        const DriverFactory* use = nullptr;
        for (const auto& x : m_sql_drivers_) {
            if (name == x->name) {
                use = x;
                break;
            }
        }

        if (use == nullptr)
            use = m_sql_drivers_.front(); // default use first

        Value(Config::KEY_SQL_DRIVER_NAME) = use->name; // update driver name

        // check version
        if (use->version != DATA_VERSION) {

            // warning!!
        }

        m_sql_driver_ = use->factory();

    }

    if (m_sql_driver_->IsConfigured()) {
        try {
            m_sql_driver_->InitConnection();
            m_sql_driver_->InitTables();
            m_sql_driver_->MigrateTables(m_sql_driver_->GetDataVersion());
            m_sql_driver_->InitPreparedStatements();
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << std::endl;
            abort();
        }
    }
}

ISqlDriver* Application::Sql() const {
    return m_sql_driver_;
}

int Application::SqlDriverCount()
{
    return static_cast<int>(m_sql_drivers_.size());
}

const char* Application::SqlDriverName(int index)
{
    if (index < 0 || index >= m_sql_drivers_.size())
        return nullptr;
    return m_sql_drivers_[index]->name;
}

void Application::Exit()
{
    m_running_.store(false);
}

void Application::ShowStatusMessage(const std::string& msg, float timeout) const
{
    Bus()->postpone(AppEvent::StatusMessage{ msg, timeout });
}
